//
//  PMSpace.m
//  PezMonkey
//
//  Created by Spence DiNicolantonio on 11/2/09.
//  Copyright 2009 Necrosoft. All rights reserved.
//

#import "PMSpace.h"
#import "PMBody.h"
#import "PMShape.h"
#import "PMJoint.h"


@implementation PMSpace

@synthesize stepsPerFrame;


#pragma mark -
#pragma mark Init

/**
 * Returns a PMSpace object initialized with zero gravity.
 */
- (id)init {
	return [self initWithGravity:PMVectZero];
}
	
/**
 * Returns a PMSpace object initialized with a given gravity vector. Upon initialization,
 * the PMShape hash ID counter will be reset.
 */
- (id)initWithGravity:(PMVect)gravity {
	if (![super init])
		return nil;
	
	// allocate and initialize cpSpace
	space = cpSpaceNew();
	
	// set default steps per frame
	[self setStepsPerFrame:1];
	
	// set gravity vector
	[self setGravity:gravity];
	
	// initialize sets
	bodies = [[NSMutableSet alloc] init];
	activeShapes = [[NSMutableSet alloc] init];
	staticShapes = [[NSMutableSet alloc] init];
	joints = [[NSMutableSet alloc] init];
	
	// reset shape ID counter
	[PMShape resetShapeIDCounter];
	
	return self;
}

	
#pragma mark -
#pragma mark Dealloc

/**
 * Releases member objects and frees memory used for cpSpace before deallocating.
 */
- (void)dealloc {
	[bodies release];
	[activeShapes release];
	[staticShapes release];
	[joints release];
	cpSpaceFree(space);
	[super dealloc];
}


#pragma mark -
#pragma mark Getters

/**
 * Returns the receiver's cpSpace reference. This is not intended for external use.
 */
- (cpSpace *)cpSpace {
	return space;
}

/**
 * Returns the number of iterations used by the receiver's impulse solver for 
 * basic collisions.
 */
- (NSUInteger)iterations {
	return space->iterations;
}

/**
 * Returns the number of iterations used by the receiver's impulse solver for 
 * elastic collisions.
 */
- (NSUInteger)elasticIterations {
	return space->elasticIterations;
}
	
/**
 * Returns the receiver's gravity vector. This vector represents both the direction
 * and magnitude (in Gs) of the receiver's simulated acceleration due to gravity.
 */
- (PMVect)gravity {
	return (PMVect) space->gravity;
}

/**
 * Returns the receiver's damping coefficient.
 */
- (PMFloat)damping {
	return space->damping;
}

/**
 * Returns the number of steps that have been taken since the receiver's creation.
 */
- (NSUInteger)steps {
	return space->stamp;
}

/**
 * Returns an array of all rigid bodies in the receiver. The order of the returned
 * array is undefined.
 */
- (NSArray *)bodies {
	return [bodies allObjects];
}

/**
 * Returns an array of all active shapes in the receiver. The order of the returned
 * array is undefined. This method simply calls the receiver'sactiveShapes method.
 */
- (NSArray *)shapes {
	return [self activeShapes];
}

/**
 * Returns an array of all active shapes in the receiver. The order of the returned
 * array is undefined.
 */
- (NSArray *)activeShapes {
	return [activeShapes allObjects];
}

/**
 * Returns an array of all static shapes in the receiver. The order of the returned
 * array is undefined.
 */
- (NSArray *)staticShapes {
	return [staticShapes allObjects];
}

/**
 * Returns an array of all static and active shapes in the receiver. The order of
 * the returned array is undefined.
 */
- (NSArray *)allShapes {
	return [[self shapes] arrayByAddingObjectsFromArray:[self staticShapes]];
}

/**
 * Returns an array of all joints in the receiver. The order of the returned array 
 * is undefined.
 */
- (NSArray *)joints {
	return [joints allObjects];
}


#pragma mark -
#pragma mark Setters

/**
 * Sets the number of iterations used by the receiver's impulse solver for basic
 * collisions to a given value.
 */
- (void)setIterations:(NSUInteger)iterations {
	space->iterations = iterations;
}

/**
 * Sets the number of iterations used by the receiver's impulse solver for elastic
 * collisions to a given value.
 */
- (void)setElasticIterations:(NSUInteger)elasticIterations {
	space->elasticIterations = elasticIterations;
}

/**
 * Sets the receiver's gravity vector to a given vector. The given vector should 
 * represents both the direction and magnitude (in Gs) of the acceleration due 
 * to gravity to be simulated by the receiver.
 */
- (void)setGravity:(PMVect)gravity {
	space->gravity = PMVectMult(gravity, PMGravityConst);
}

/**
 * Sets the receiver's damping coefficient to a given value.
 */
- (void)setDamping:(PMFloat)damping {
	space->damping = damping;
}


#pragma mark -
#pragma mark Add Elements

/**
 * Adds a given rigid body to the receiver.
 */
- (void)addBody:(PMBody *)body {
	// already in space?
	if ([self containsBody:body])
		return;
	
	// add body to set
	[bodies addObject:body];
	
	// add body's referenced cpBody to cpSpace
	cpSpaceAddBody(space, body.cpBody);
}


/**
 * Adds a given collision shape to the receiver. If the static flag is set to 
 * YES, then the given shape will be added as a static shape; otherwise, the 
 * shape will be active.
 */
- (void)addShape:(PMShape *)shape isStatic:(BOOL)isStatic {
	if (isStatic)
		[self addStaticShape:shape];
	else
		[self addShape:shape];
}

/**
 * Adds a given collision shape to the receiver as an active shape. If the given 
 * collision shape is already contained in the receiver as a static shape, the
 * shape is made active. This method simply calls the receiver's addActiveShape:
 * method
 */
- (void)addShape:(PMShape *)shape {
	[self addActiveShape:shape];
}

/**
 * Adds a given collision shape to the receiver as an active shape. If the given 
 * collision shape is already contained in the receiver as a static shape, the
 * shape is made active.
 */
- (void)addActiveShape:(PMShape *)shape {
	// already in space as active?
	if ([self containsShape:shape])
		return;
	
	// already in space as static?
	if ([self containsStaticShape:shape])
		[self removeShape:shape];
	
	// add shape to set
	[activeShapes addObject:shape];
	
	// add shape's referenced cpShape to cpSpace
	cpSpaceAddShape(space, shape.cpShape);
}

/**
 * Adds a given collision shape to the receiver as a static shape. If the given 
 * collision shape is already contained in the receiver as an active shape, the
 * shape is made static.
 */
- (void)addStaticShape:(PMShape *)shape {
	// already in space as static?
	if ([self containsStaticShape:shape])
		return;
	
	// already in space as active?
	if ([self containsShape:shape])
		[self removeShape:shape];
	
	// add shape to set
	[staticShapes addObject:shape];
	
	// add shape's referenced cpShape to cpSpace
	cpSpaceAddStaticShape(space, shape.cpShape);
}

/**
 * Adds a given joint to the receiver.
 */
- (void)addJoint:(PMJoint *)joint {
	// already in space?
	if ([self containsJoint:joint])
		return;
	
	// add joint to set
	[joints addObject:joint];
	
	// add joint's referenced cpJoint to cpSpace
	cpSpaceAddJoint(space, joint.cpJoint);
}


#pragma mark -
#pragma mark Remove Elements

/**
 * Removes a given rigid body from the receiver.
 */
- (void)removeBody:(PMBody *)body {
	// not in space?
	if (![self containsBody:body])
		return;
	
	// remove body from set
	[bodies removeObject:body];
	
	// remove body's referenced cpBody from cpSpace
	cpSpaceRemoveBody(space, body.cpBody);
}

/**
 * Removes a given collision shape from the receiver. This method will remove
 * the given shape whether or not it is static.
 */
- (void)removeShape:(PMShape *)shape {
	// not in space?
	if (![self containsShape:shape] && ![self containsStaticShape:shape])
		return;
	
	// remove shape from set
	[activeShapes removeObject:shape];
	[staticShapes removeObject:shape];
	
	// remove shape's referenced cpShape from cpSpace
	cpSpaceRemoveShape(space, shape.cpShape);
	cpSpaceRemoveStaticShape(space, shape.cpShape);
}

/**
 * Removes a given collision shape and its corresponding rigid body from the 
 * receiver. This method will remove the given shape whether or not it is static.
 */
- (void)removeShapeAndBody:(PMShape *)shape {
	[self removeBody:shape.body];
	[self removeShape:shape];
}

/**
 * Removes a given joint from the receiver.
 */
- (void)removeJoint:(PMJoint *)joint {
	// not in space?
	if (![self containsJoint:joint])
		return;
	
	// remove joint from set
	[joints removeObject:joint];
	
	// remove joint's referenced cpJoint from cpSpace
	cpSpaceRemoveJoint(space, joint.cpJoint);
}
	 
/**
 * Removes all rigid bodies from the receiver.
 */
- (void)removeAllBodies {
	for (PMBody *body in bodies)
		[self removeBody:body];
}

/**
 * Removes all collision shapes from the receiver.
 */
- (void)removeAllShapes {
	for (PMShape *shape in activeShapes)
		[self removeShape:shape];
	for (PMShape *shape in staticShapes)
		[self removeShape:shape];
}

/**
 * Removes all joints from the receiver.
 */
- (void)removeAllJoints {
	for (PMJoint *joint in joints)
		[self removeJoint:joint];
}

/**
 * Removes all rigid bodies, collision shapes, and joints from the receiver.
 */
- (void)removeAll {
	[self removeAllJoints];
	[self removeAllShapes];
	[self removeAllBodies];
}


#pragma mark -
#pragma mark Queries

/**
 * Returns YES if the given rigid body is in the receiver; NO otherwise.
 */
- (BOOL)containsBody:(PMBody *)body {
	return [bodies containsObject:body];
}

/**
 * Returns YES if the given collision shape is an active shape in the receiver;
 * NO otherwise. This method simply calls the receiver's containsActiveShape: 
 * method and returns the result.
 */
- (BOOL)containsShape:(PMShape *)shape {
	return [self containsActiveShape:shape];
}

/**
 * Returns YES if the given collision shape is an active shape in the receiver;
 * NO otherwise.
 */
- (BOOL)containsActiveShape:(PMShape *)shape {
	return [activeShapes containsObject:shape];
}

/**
 * Returns YES if the given collision shape is a static shape in the receiver;
 * NO otherwise.
 */
- (BOOL)containsStaticShape:(PMShape *)shape {
	return [staticShapes containsObject:shape];
}

/**
 * returns YES if the given joint is in the receiver; NO otherwise.
 */
- (BOOL)containsJoint:(PMJoint *)joint {
	return [joints containsObject:joint];
}


#pragma mark -
#pragma mark Spacial Hash

/**
 * Sets the size of the spacial hash for active collision shapes to a given value 
 * with a given cell dimension. By default, the active hash size is set to 1000, 
 * with cell dimension set to 100.0. 
 * 
 * In general, the hash size should be set to 10x the number of expected active 
 * collision shapes and the cell dimension should be set to about half of the 
 * average shape size.
 */
- (void)setActiveHashSize:(NSUInteger)size cellDimension:(PMFloat)dimension {
	cpSpaceResizeActiveHash(space, dimension, size);
}

/**
 * Sets the size of the spacial hash for static collision shapes to a given value 
 * with a given cell dimension. By default, the active hash size is set to 1000, 
 * with cell dimension set to 100.0. 
 * 
 * In general, the hash size should be set to 10x the number of expected static 
 * collision shapes and the cell dimension should be set to about half of the 
 * average shape size.
 */
- (void)setStaticHashSize:(NSUInteger)size cellDimension:(PMFloat)dimension {
	cpSpaceResizeStaticHash(space, dimension, size);
}

/**
 * Rehashes all static shapes. This method should be called whenever static shapes
 * are moved.
 */
- (void)rehashStatic {
	cpSpaceRehashStatic(space);
}


#pragma mark -
#pragma mark Step

/**
 * Updates the receiver, by stepping through physics simulation of a given time
 * interval. The backend Chipmunk engine will be stepped stepsPerFrame times.
 */
- (void)step:(NSTimeInterval)deltaT {
	PMFloat stepTime = (deltaT / (PMFloat)stepsPerFrame);
	for (int i=0; i<stepsPerFrame; i++)
		cpSpaceStep(space, stepTime);
}

@end
